/*
  author Sylvain Bertrand <sylvain.bertrand@gmail.com>
  Protected by linux GNU GPLv2
  Copyright 2012-2014
*/
#include <linux/pci.h>
#include <linux/cdev.h>
#include <asm/unaligned.h>

#include <alga/rng_mng.h>
#include <uapi/alga/pixel_fmts.h>
#include <alga/timing.h>
#include <alga/amd/atombios/atb.h>
#include <uapi/alga/amd/dce6/dce6.h>
#include <alga/amd/atombios/vm.h>
#include <alga/amd/atombios/cm.h>
#include <alga/amd/atombios/pp.h>
#include <alga/amd/atombios/vram_info.h>

#include "../mc.h"
#include "../rlc.h"
#include "../ih.h"
#include "../fence.h"
#include "../ring.h"
#include "../dmas.h"
#include "../ba.h"
#include "../cps.h"
#include "../gpu.h"
#include "../drv.h"

#include "../smc_tbls.h"

#include "../regs.h"

#include "ctx.h"
#include "private.h"
#include "smc_lvl.h"
#include "smc_volt.h"

void smc_state_tbl_initial_init(struct ctx *ctx, struct smc_state_tbl *tbl)
{
	struct dev_drv_data *dd;
	struct smc_lvl *lvl;
	u32 val;

	LOG("initial smc table init");

	lvl = &tbl->initial_lvl;
	dd = pci_get_drvdata(ctx->dev);

	/*--------------------------------------------------------------------*/
	/* memory clock registers and value */
	put_unaligned_be32(dd->pp.clks_regs.dll_ctl, &lvl->mem_clk.dll_ctl);
	put_unaligned_be32(dd->pp.clks_regs.mem_clk_pm_ctl,
						&lvl->mem_clk.mem_clk_pm_ctl);
	put_unaligned_be32(dd->pp.clks_regs.mem_pll_ad_func_ctl,
					&lvl->mem_clk.mem_pll_ad_func_ctl);
	put_unaligned_be32(dd->pp.clks_regs.mem_pll_dq_func_ctl,
					&lvl->mem_clk.mem_pll_dq_func_ctl);
	put_unaligned_be32(dd->pp.clks_regs.mem_pll_func_ctl_0,
					&lvl->mem_clk.mem_pll_func_ctl_0);
	put_unaligned_be32(dd->pp.clks_regs.mem_pll_func_ctl_1,
					&lvl->mem_clk.mem_pll_func_ctl_1);
	put_unaligned_be32(dd->pp.clks_regs.mem_pll_func_ctl_2,
					&lvl->mem_clk.mem_pll_func_ctl_2);
	put_unaligned_be32(dd->pp.clks_regs.mem_pll_ss_0,
						&lvl->mem_clk.mem_pll_ss_0);
	put_unaligned_be32(dd->pp.clks_regs.mem_pll_ss_1,
						&lvl->mem_clk.mem_pll_ss_1);

	put_unaligned_be32(dd->pp.default_mem_clk, &lvl->mem_clk.clk);
	/*--------------------------------------------------------------------*/

	/*--------------------------------------------------------------------*/
	/* engine clock registers and value */
	put_unaligned_be32(dd->pp.clks_regs.cg_eng_pll_func_ctl_0,
					&lvl->eng_clk.cg_eng_pll_func_ctl_0);
	put_unaligned_be32(dd->pp.clks_regs.cg_eng_pll_func_ctl_1,
					&lvl->eng_clk.cg_eng_pll_func_ctl_1);
	put_unaligned_be32(dd->pp.clks_regs.cg_eng_pll_func_ctl_2,
					&lvl->eng_clk.cg_eng_pll_func_ctl_2);
	put_unaligned_be32(dd->pp.clks_regs.cg_eng_pll_func_ctl_3,
					&lvl->eng_clk.cg_eng_pll_func_ctl_3);
	put_unaligned_be32(dd->pp.clks_regs.cg_eng_pll_ss_0,
						&lvl->eng_clk.cg_eng_pll_ss_0);
	put_unaligned_be32(dd->pp.clks_regs.cg_eng_pll_ss_1,
						&lvl->eng_clk.cg_eng_pll_ss_1);

	put_unaligned_be32(dd->pp.default_eng_clk, &lvl->eng_clk.clk);
	/*--------------------------------------------------------------------*/

	lvl->mc_arb_set_idx = MC_ARB_SET_IDX_INITIAL_EMERGENCY;
	lvl->mc_reg_set_idx = MC_REG_SET_IDX_INITIAL_EMERGENCY;

	if (ctx->volt_caps & VOLT_CAPS_VDDC_CTL_ENA) {
		smc_volt_vddc_set_from_atb_mv(ctx, &lvl->vddc,
							dd->pp.default_vddc);
		smc_volt_std_vddc_compute(ctx, &lvl->std_vddc, &lvl->vddc);
	}

	if (ctx->volt_caps & VOLT_CAPS_VDDCI_CTL_ENA)
		smc_volt_vddci_set_from_atb_mv(ctx, &lvl->vddci,
							dd->pp.default_vddci);

	if (ctx->volt_caps & VOLT_CAPS_VDDC_PHASE_SHED_CTL_ENA) {
		u8 i;

		for (i = 0; i < ctx->atb_vddc_phase_shed_limits_tbl.entries_n;
									++i) {
			struct atb_vddc_phase_shed_limits *limit;

			limit = &ctx->atb_vddc_phase_shed_limits_tbl.entries[i];
			if ((limit->vddc_mv >= dd->pp.default_vddc)
				&& (limit->sclk >= dd->pp.default_eng_clk)
				&& (limit->mclk >= dd->pp.default_mem_clk))
				break;
		}
		lvl->vddc.phase_settings = i;
	}

	if (ctx->volt_caps & VOLT_CAPS_MVDD_CTL_ENA)
		 smc_volt_mvdd_set_from_atb_mv(ctx, &lvl->mvdd,
							dd->pp.default_mvddc);

	val = set(CAT_CG_R, 0xffff) | set(CAT_CG_L, 0);
	put_unaligned_be32(val, &lvl->a_t);

	put_unaligned_be32(ctx->d_sp, &lvl->b_sp);
	lvl->pcie_gen = dd->pp.default_pcie_gen;

	if (ctx->misc_caps & MISC_CAPS_VRAM_IS_GDDR5) {
		lvl->strobe_mode = gddr5_strobe_mode_compute(ctx,
							dd->pp.default_mem_clk);

		if (dd->pp.default_mem_clk > MEM_CLK_EDC_RD_ENA_THRESHOLD)
			lvl->mc_flgs |= SMC_MC_FLGS_EDC_RD;
		if (dd->pp.default_mem_clk > MEM_CLK_EDC_WR_ENA_THRESHOLD)
			lvl->mc_flgs |= SMC_MC_FLGS_EDC_WR;
	}

	tbl->initial.lvls_n = 1;
	tbl->initial.flgs |= SMC_SW_STATE_FLGS_DC;

	lvl->dpm_to_perf_lvl.max_pulse_skip = 0;
	lvl->dpm_to_perf_lvl.near_tdp_dec = 0;
	lvl->dpm_to_perf_lvl.above_safe_inc = 0;
	lvl->dpm_to_perf_lvl.below_safe_inc = 0;
	lvl->dpm_to_perf_lvl.pwr_efficiency_ratio = 0;

	/* set everything to the max value, namely all bits to 1 */
	put_unaligned_be32(SPT0_PWR_MIN | SPT0_PWR_MAX,
						&lvl->sq_pwr_throttle_0);

	put_unaligned_be32(SPT1_PWR_DELTA_MAX | SPT1_STI_SZ | SPT1_LTI_RATIO,
						&lvl->sq_pwr_throttle_1);
}
